﻿//GLManager:webGL上下文以及渲染相关的管理器


//以下均为全局变量
var g_testValue;                      //测试数值
var g_RenderSize;                     //体渲染视图尺寸
var g_tex_VolumeData;                //体数据的纹理数据
var g_tex_TransFunc;                 //传递函数的纹理数据
var g_v3_volSize;                    //体数据的纹理尺寸
var g_EysPos;                         //观察位置
var g_ViewX;                          //观察方向右方
var g_ViewY;                          //观察方向上方
var g_ViewZ;                          //观察方向前方
var g_sampleType;                    //采样方式（临近或线性）
var g_step = 0.01;                     //渲染采样的步数
var g_GlobalOpacity;                 //全局渲染不透明度
var g_SurfaceIncreaseValue = 100.0; //表面增强系数
var g_LightIntensity;                //光照强度（xyz分别是环境光，漫反射，镜面反射
var g_LightPos;                        //光照方向
var g_ContPreKt = 1.0;                 //环境保留的Kt参数
var g_ContPreKs = 1.0;                //环境保留的Ks参数
var g_FresnelColor;                   //菲涅尔边缘颜色
var g_FresnelValue = 0.5;                   //菲涅尔边缘值

var g_FirstInit = true;           //是否是第一次初始化(不想让用户改变画布尺寸后再次初始化，因为那样会把一些变量给清除)

function GLManager()
{
    var canvas; //画布节点
    var gl;     //webGL上下文
    var prg;    //程序对象

    //以下变量为着色器中uniform变量的位置，必须知道这个位置才能给其传送数据
    var testValueUni;               //测试数值
    var volumeDataUni;              //体数据
    var TransFuncDataUni;           //传递函数数据
    var volSizeUni;                 //体数据的尺寸
    var EyePosUni                   //观察位置
    var VXUni;                      //观察方向右方
    var VYUni;                      //观察方向上方
    var VZUni;                      //观察方向前方
    var samplerTypeUni;             //采样方式（临近或线性）
    var stepUni;                    //步数
    var GlobalOpacityUni;           //全局不透明度
    var SurfaceIncreaseEnableUni;   //启用表面增强
    var SurfaceIncreaseValueUni;    //表面增强系数
    var LightEnableUni;             //启用光照效果
    var LightIntensityUni;          //光照强度
    var LightPosUni;                //光照方向
    var ContPreEnableUni;           //启用环境保留效果
    var ContPreKtUni;               //环境保留的Kt参数
    var ContPreKsUni;               //环境保留的Ks参数
    var FresnelEnableUni;           //启用菲涅尔边缘效果
    var FresnelColorUni;            //菲涅尔边缘颜色
    var FresnelValueUni;            //菲涅尔边缘值


    this.init = function()//初始化
    {
        canvas = document.getElementById('VolRenderCanvas');//从HTML文档中找到画布
        canvas.width = g_RenderSize;
        canvas.height = g_RenderSize;

        gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');//获取webGL上下文

        prg = create_program();             //创建程序对象
        SetPositionAttribute();             //设置顶点位置的Attribute变量

        initUniformLocation();
        initGlobalVars();
    };

    this.render = function()           //渲染（重要函数，所有渲染相关操作都在这个函数中执行，随项目进行会慢慢扩充）
    {
        gl.clearColor(0.0, 1.0, 0.0, 1.0);                  //gl初始颜色
        gl.clearDepth(1.0);                                 //gl初始深度
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);//执行清除命令将颜色和深度变为初始值

        gl.uniform1f(testValueUni, g_testValue);//将测试数值传入着色器

        //将渲染需要的三维向量放入着色器对应的位置
        SetUniformVec3(volSizeUni, g_v3_volSize);
        SetUniformVec3(EyePosUni, g_EysPos);
        SetUniformVec3(VXUni, g_ViewX);
        SetUniformVec3(VYUni, g_ViewY);
        SetUniformVec3(VZUni, g_ViewZ);
        SetUniformVec3(LightIntensityUni,g_LightIntensity);
        SetUniformVec3(FresnelColorUni,g_FresnelColor);

        if(FoldDivShow[8])          //如果自定义光源
            SetUniformVec3(LightPosUni,g_LightPos);
        else                        //否则将光源设置为视点
        {
            var light = createVector3();
            FillVec3Data(light,g_EysPos[0],g_EysPos[1],g_EysPos[2]);
            Normalize(light);
            SetUniformVec3(LightPosUni,light);
        }

        //放入所需要的float变量:
        gl.uniform1f(samplerTypeUni, g_sampleType);
        gl.uniform1f(stepUni, g_step);
        gl.uniform1f(GlobalOpacityUni,g_GlobalOpacity);
        gl.uniform1f(SurfaceIncreaseValueUni,g_SurfaceIncreaseValue);
        gl.uniform1f(ContPreKtUni,g_ContPreKt);
        gl.uniform1f(ContPreKsUni,g_ContPreKs);
        gl.uniform1f(FresnelValueUni,g_FresnelValue);


        //设置效果是否开启
        gl.uniform1f(SurfaceIncreaseEnableUni, FoldDivShow[5]? 1.0:0.0);
        gl.uniform1f(LightEnableUni, FoldDivShow[6]? 1.0:0.0);
        gl.uniform1f(ContPreEnableUni,FoldDivShow[7]? 1.0:0.0);
        gl.uniform1f(FresnelEnableUni,FoldDivShow[9]? 1.0:0.0);

        //将g_tex_VolumeData纹理传入着色器相应位置
        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(gl.TEXTURE_2D, g_tex_VolumeData);
        gl.uniform1i(volumeDataUni, 0);

        //将g_tex_TransFunc纹理传入着色器相应位置
        gl.activeTexture(gl.TEXTURE1);
        gl.bindTexture(gl.TEXTURE_2D, g_tex_TransFunc);
        gl.uniform1i(TransFuncDataUni, 1);

        gl.drawArrays(gl.TRIANGLES, 0, 6);                  //开始渲染顶点，数目为6个

        gl.flush();                                         //所有渲染完毕，刷新
    };

    function create_vbo(data)//创建顶点缓冲对象（Vertex Buffer Object）
    {
        var vbo = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
        gl.bindBuffer(gl.ARRAY_BUFFER, null);
        return vbo;
    }
    function set_attribute(vbo, attL, attS)//设置顶点着色器中Attribute变量的值
    {
        gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
        gl.enableVertexAttribArray(attL);
        gl.vertexAttribPointer(attL, attS, gl.FLOAT, false, 0, 0);
    }
    function SetPositionAttribute()
    {
        //写入顶点数据，在这个项目中，顶点数据只是简单的两个三角形，而这两个三角形拼成一个正方形，占满整个画布
        var position = [
            1.0, 1.0, 0.0,
            -1.0 , 1.0, 0.0,
            -1.0 , -1.0, 0.0,
            1.0 , 1.0, 0.0,
            -1.0, -1.0, 0.0,
            1.0, -1.0, 0.0
        ];
        var vboPosition = create_vbo(position);

        //set_attribute将数据传递给顶点着色器的Attribute变量，
        //第一个参数是数据，第二个参数是Attribute变量在着色器中的位置，第三个参数是数据的Stride，
        //这里的位置是x,y,z三个量，所以Stride为3。
        set_attribute(vboPosition, gl.getAttribLocation(prg, 'position'), 3);
    }

    function create_shader(id) {
        var shader;

        var scriptElement = document.getElementById(id);
        if (!scriptElement)
            return;

        switch (scriptElement.type)
        {
            case 'x-shader/x-vertex':
                shader = gl.createShader(gl.VERTEX_SHADER);
                break;

            case 'x-shader/x-fragment':
                shader = gl.createShader(gl.FRAGMENT_SHADER);
                break;
        }

        gl.shaderSource(shader, scriptElement.textContent);
        gl.compileShader(shader);

        if (gl.getShaderParameter(shader, gl.COMPILE_STATUS))
            return shader;
        else
            alert(gl.getShaderInfoLog(shader));
    }

    function create_program() {
        var program = gl.createProgram();

        var v_shader = create_shader('VSCode');     //获取顶点着色器
        var f_shader = create_shader('FSCode');     //获取片段着色器

        gl.attachShader(program, v_shader);
        gl.attachShader(program, f_shader);

        gl.linkProgram(program);

        if (gl.getProgramParameter(program, gl.LINK_STATUS))
        {
            gl.useProgram(program);
            return program;
        }
        else
            alert(gl.getProgramInfoLog(program));
    }

    function initUniformLocation()      //找到着色器中uniform变量的位置
    {
        volumeDataUni = gl.getUniformLocation(prg, 'volumeData');
        TransFuncDataUni = gl.getUniformLocation(prg, 'TransFuncData');
        testValueUni = gl.getUniformLocation(prg, 'testValue');
        volSizeUni = gl.getUniformLocation(prg, 'VolDataSize');
        EyePosUni = gl.getUniformLocation(prg, "EyePos");
        VXUni = gl.getUniformLocation(prg, "ViewXAxis");
        VYUni = gl.getUniformLocation(prg, "ViewYAxis");
        VZUni = gl.getUniformLocation(prg, "ViewZAxis");
        samplerTypeUni = gl.getUniformLocation(prg, "sampleType");
        stepUni = gl.getUniformLocation(prg, "step");
        GlobalOpacityUni = gl.getUniformLocation(prg, "GlobalOpacity");
        SurfaceIncreaseEnableUni = gl.getUniformLocation(prg, "SurfaceIncreaseEnable");
        SurfaceIncreaseValueUni = gl.getUniformLocation(prg, "SurfaceIncreaseValue");
        LightEnableUni = gl.getUniformLocation(prg, "LightEnable");
        LightIntensityUni = gl.getUniformLocation(prg, "LightIntensity");
        LightPosUni = gl.getUniformLocation(prg, "LightPos");
        ContPreEnableUni = gl.getUniformLocation(prg, "ContPreEnable");
        ContPreKtUni = gl.getUniformLocation(prg, "ContPre_Kt");
        ContPreKsUni = gl.getUniformLocation(prg, "ContPre_Ks");
        FresnelEnableUni = gl.getUniformLocation(prg, "FresnelEnable");
        FresnelColorUni = gl.getUniformLocation(prg, "FresnelColor");
        FresnelValueUni = gl.getUniformLocation(prg, "FresnelValue");
    }

    function initGlobalVars()           //初始化一些全局变量
    {
        if(g_FirstInit==false)
            return ;

        g_v3_volSize = createVector3();
        FillVec3Data(g_v3_volSize, 1.0, 1.0, 1.0);
        g_EysPos = createVector3();
        FillVec3Data(g_EysPos, 0.0, 0.0, -3.0);
        g_ViewX = createVector3();
        FillVec3Data(g_ViewX, 1.0, 0.0, 0.0);
        g_ViewY = createVector3();
        FillVec3Data(g_ViewY, 0.0, 1.0, 0.0);
        g_ViewZ = createVector3();
        FillVec3Data(g_ViewZ, 0.0, 0.0, 1.0);
        g_LightIntensity = createVector3();
        FillVec3Data(g_LightIntensity, 0.5, 2.5, 0.0);
        g_LightPos = createVector3();
        FillVec3Data(g_LightPos,1.0,0.0,0.0);
        g_FresnelColor = createVector3();
        FillVec3Data(g_FresnelColor,1.0,0.0,0.0);
        g_FirstInit = false;
    }

    function SetUniformVec3(loc,vec)            //将一个三维向量放入着色器中uniform变量中
    {
        gl.uniform3f(loc, vec[0], vec[1], vec[2]);
    }
};
